﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using Bitcoin.Cryptography;
using Bitcoin.Discovery;
using Bitcoin.Net;
using Bitcoin.Store;

namespace Bitcoin.Examples
{
    // Example: DNS Peer:  
    // .\Bitcoin.Net40.Examples.exe PaybackService2 5K9MPQNfEdLhb3i6Cq2rRqxQ1u5zHAFDUBhJFFtET5Ns1pb7n6Q 
    
    //"70.180.210.24:8333"

    // TestNet
    // Private key is: 92RzsCigNYJDVvmyNLnoaQgMaBmSSgzH1JcqjiJ4qczLm3gW2x9
    // Address from private key is: n1hqqGrGZnL2XSKokFgqXXz4A6PDCc19TW
    //  .\Bitcoin.Net40.Examples.exe PaybackService2 testnet 92RzsCigNYJDVvmyNLnoaQgMaBmSSgzH1JcqjiJ4qczLm3gW2x9 "127.0.0.1:18333"

    /// <summary>
    /// PaybackService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply
    /// sends them right back to the previous owner, determined rather arbitrarily by the address of the first input.
    /// </summary>
    /// <remarks>
    /// If running on TestNet (slow but better than using real coins on ProdNet) do the following:
    /// <ol>
    ///   <li>Backup your current wallet.dat in case of unforeseen problems</li>
    ///   <li>Start your bitcoin client in test mode <code>bitcoin -testnet</code>. This will create a new sub-directory called testnet and should not interfere with normal wallets or operations.</li>
    ///   <li>(Optional) Choose a fresh address</li>
    ///   <li>(Optional) Visit the TestNet faucet (https://testnet.freebitcoins.appspot.com/) to load your client with test coins</li>
    ///   <li>Run <code>BitCoinSharp.Examples PingService -testnet</code></li>
    ///   <li>Wait for the block chain to download</li>
    ///   <li>Send some coins from your bitcoin client to the address provided in the PingService console</li>
    ///   <li>Leave it running until you get the coins back again</li>
    /// </ol><p/>
    /// The testnet can be slow or flaky as it's a shared resource. You can use the <a href="http://sourceforge.net/projects/bitcoin/files/Bitcoin/testnet-in-a-box/">testnet in a box</a>
    /// to do everything purely locally.
    /// </remarks>
    public static class PaybackService2
    {
        public static void Run(string[] args)
        {
            var testNet = args.Length > 0 &&
                          string.Equals(args[0], "testnet", StringComparison.InvariantCultureIgnoreCase);
            var parameters = testNet ? ChainParameters.TestNet() : ChainParameters.ProdNet();

            // Decode the private key from Satoshi's Base58 variant. If 51 characters long then it's from BitCoins
            // "dumpprivkey" command and includes a version byte and checksum. Otherwise assume it's a raw key.
            string argKey = testNet ? args[1] : args[0];
            KeyPair key = null;
            if (argKey.Length == 51)
            {
                var dumpedPrivateKey = new BitcoinSecret(parameters, argKey);
                key = dumpedPrivateKey.Key;
            }
            Console.WriteLine("Address from private key is: " + Temp_EcKey.ToAddress(key, parameters));

            // Import the private key to a fresh wallet.
            var wallet = new Wallet(parameters);
            wallet.AddKey(key);

            Console.WriteLine(wallet);

            // Find the transactions that involve those coins.
            using (var blockStore = new MemoryBlockStore(parameters))
            {
                // Connect to the localhost node. One minute timeout since we won't try any other peers
                Console.WriteLine("Connecting ...");   
                var chain = new BlockChain(parameters, wallet, blockStore);

                var peerGroup = new PeerGroupA(blockStore, parameters, chain);

                NetEndPoint peer;
                if (!testNet && args.Length >= 2 || args.Length >= 3)
                {
                    var peerArg = testNet ? args[2] : args[1];
                    peer = NetEndPoint.Parse(peerArg);
                    Console.WriteLine("Peer: {0}", peer);
                    var firstPeerAddress = new PeerAddressA(IPAddress.Parse(peer.Address.ToString()), peer.Port);
                    peerGroup.AddAddress(firstPeerAddress);
                }
                else
                {
                    var seed = new SeedPeers(parameters.Port);
                    
                    var dnsService = new FrameworkDnsService();
                    var dns = new DnsDiscovery(dnsService, parameters.Port);
                    //var peers = dns.GetPeers();

                    var tcpService = new FrameworkTcpService();
                    var irc = new IrcDiscovery(tcpService, "#bitcoin");
                    //var peers = d.GetPeers();

                    //peer = peers.First();

                    peerGroup.AddPeerDiscovery(seed);
                    peerGroup.AddPeerDiscovery(dns);
                    peerGroup.AddPeerDiscovery(irc);
                }

                peerGroup.Start();

                // We want to know when the balance changes.
                wallet.CoinsReceived +=
                    (sender, e) =>
                    {
                        // Running on a peer thread.
                        Debug.Assert(!e.NewBalance.Equals(0));
                        // It's impossible to pick one specific identity that you receive coins from in BitCoin as there
                        // could be inputs from many addresses. So instead we just pick the first and assume they were all
                        // owned by the same person.
                        var input = e.Tx.Inputs[0];
                        var fromPubKey = input.ScriptSig.PubKey;
                        var fromHash = SecurityUtility.Sha256Hash160(fromPubKey);
                        var from = new BitcoinAddress((byte) parameters.AddressHeader, fromHash);
                        var value = wallet.GetValueSentToMe(e.Tx);
                        Console.WriteLine("Received " + Temp_BitcoinUtility.BitcoinValueToFriendlyString(value) +
                                          " from " + from);
                        // Now send the coins back!
                        var sendTx = Temp_WalletSend.SendCoins(wallet, peerGroup, from, value);
                        Debug.Assert(sendTx != null); // We should never try to send more coins than we have!
                        Console.WriteLine("Sent coins back! Transaction hash is " + sendTx.HashAsString);
                    };

                Console.WriteLine("Downloading block chain ...");
                peerGroup.DownloadBlockChain();
                Console.WriteLine("Send coins to: " + Temp_EcKey.ToAddress(key, parameters));
                Console.WriteLine("Waiting for coins to arrive. Press Ctrl-C to quit.");
                // The PeerGroup thread keeps us alive until something kills the process.
            }
        }
    }
}
